package org.zoomdev.zoom.ioc;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.utils.Classes;
import org.zoomdev.zoom.ioc.annotations.Config;
import org.zoomdev.zoom.ioc.annotations.Inject;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Objects;

public abstract class IocKey {

    public static final IocKey[] NONE_PARAMETER = new IocKey[0];

    private final String name;
    private final Type type;
    int h;

    public IocKey(Type type) {
        this(type, null);
    }

    public IocKey(Type type, String name) {
        assert (type != null || name != null);

        if (StringUtils.isEmpty(name)) {
            name = null;
        }


        this.name = name;
        this.type = type;
    }

    public static Object[] getValues(Ioc ioc, IocKey[] values) {
        Object[] args = new Object[values.length];
        for (int i = 0; i < values.length; ++i) {
            args[i] = values[i].getValue(ioc);
        }
        return args;
    }

    public static IocKey[] fromMethod(Method method) {
        return fromParameters(method.getGenericParameterTypes(), method.getParameterAnnotations());
    }

    public static IocKey[] valuesFromMethod(Method method) {

        return fromParameters(method.getGenericParameterTypes(), method.getParameterAnnotations());
    }

    public static IocKey[] fromConstructor(IocKey key, Constructor<?> constructor) {
        Type[] resultTypes;
        if (IocUtils.isGenericType(key)) {
            Type[] types = constructor.getGenericParameterTypes();
            Class clazz = (Class) ((ParameterizedType) key.getType()).getRawType();  //constructor.getDeclaringClass();
            TypeVariable[] typeVariables = clazz.getTypeParameters();
            resultTypes = getRealTypes(key, types, typeVariables);
        } else {
            resultTypes = constructor.getGenericParameterTypes();
            Class<?>[] types = constructor.getParameterTypes();
            if (types.length != resultTypes.length) {
                throw new IocException("不支持非静态内部类:" + key + " 直接创建,请提供对应的内部类Module或者为此类提供一个无参构造函数, 构造函数:" + constructor);
            }

        }
        return fromParameters(resultTypes, constructor.getParameterAnnotations());
    }

    public static IocKey[] fromMethod(IocKey key, Method method) {
        Type[] resultTypes;
        if (IocUtils.isGenericType(key)) {
            Type[] types = method.getGenericParameterTypes();//constructor.getDeclaringClass();
            TypeVariable[] typeVariables = method.getTypeParameters();
            resultTypes = getRealTypes(key, types, typeVariables);
        } else {
            resultTypes = method.getGenericParameterTypes();
        }
        return fromParameters(resultTypes, method.getParameterAnnotations());
    }

    /**
     * 将type替换出去
     *
     * @param key
     * @return
     */
    public static Type[] getRealTypes(IocKey key, Type[] types, TypeVariable[] typeVariables) {
        Type[] keyTypes = Classes.getParameterizedTypes(key.getType());
        Type[] result = new Type[types.length];
        int i = 0;
        for (Type constructorType : types) {
            result[i] = getRealType(constructorType, keyTypes, typeVariables);
            ++i;
        }
        return result;
    }

    public static boolean checkTypeVariable(ParameterizedType type) {
        for (Type t : type.getActualTypeArguments()) {
            if (t instanceof ParameterizedType) {
                if (checkTypeVariable((ParameterizedType) t)) {
                    return true;
                }
            } else if (t instanceof TypeVariable) {
                return true;
            }
        }

        return false;
    }

    public static Type getRealTypeOfField(Type type, IocKey key) {
        if (IocUtils.isGenericType(key) && type instanceof ParameterizedType) {
            Type[] keyTypes = Classes.getParameterizedTypes(key.getType());
            Class clazz = (Class) ((ParameterizedType) key.getType()).getRawType();  //constructor.getDeclaringClass();
            TypeVariable[] classTypes = clazz.getTypeParameters();
            return getRealType(type, keyTypes, classTypes);
        }

        if (type instanceof IocUtils.GenericType) {
            return type;
        }

        if (type instanceof ParameterizedType) {
            //是否有T泛型
            if (checkTypeVariable((ParameterizedType) type)) {
                return ((ParameterizedType) type).getRawType();
            }

            return IocUtils.createGenericType(type);
        }

        return type;

    }

    public static Type getRealType(Type type, Type[] keyTypes, TypeVariable[] classTypes) {

        if (type instanceof TypeVariable) {
            int index = ArrayUtils.indexOf(classTypes, type);
            return keyTypes[index];
        } else if (type instanceof Class) {
            //继续查找
            return type;
        } else if (type instanceof ParameterizedType) {
            //替换泛型为实际
            return replaceGenericType((ParameterizedType) type, keyTypes, classTypes);
        } else {
            throw new ZoomException("产生非预期情况");
        }
    }

    /**
     * 检查是否有全部依赖
     *
     * @param types
     * @param methodAnnotations
     * @return
     */
    public static boolean checkParameters(Type[] types, Annotation[][] methodAnnotations) {
        for (int i = 0; i < types.length; ++i) {
            Type type = types[i];
            Inject paramInject = null;
            Config config = null;
            Annotation[] annotations = methodAnnotations[i];
            for (Annotation annotation : annotations) {
                if (annotation instanceof Inject) {
                    paramInject = (Inject) annotation;
                } else if (annotation instanceof Config) {
                    config = (Config) annotation;
                }
            }
            if (!checkParameter(type, paramInject, config)) {
                return false;
            }
        }
        return true;
    }

    public static IocKey[] fromParameters(Type[] types, Annotation[][] methodAnnotations) {
        IocKey[] keys = new IocKey[types.length];
        for (int i = 0; i < types.length; ++i) {
            Type type = types[i];
            Inject paramInject = null;
            Config config = null;
            Annotation[] annotations = methodAnnotations[i];
            for (Annotation annotation : annotations) {
                if (annotation instanceof Inject) {
                    paramInject = (Inject) annotation;
                } else if (annotation instanceof Config) {
                    config = (Config) annotation;
                }
            }
            keys[i] = fromParameter(type, paramInject, config);
        }
        return keys;
    }

    /**
     * 直接构建一个TypeKey， （泛型）
     * @param rawType       原始类型 ,例如 : List.class
     * @param types         泛型类型 , 例如 : new Class[](Integer.class)
     * @return
     */
    public static IocKey genericType(Class<?> rawType,Type[] types) {
        return new IocTypeKey(new IocUtils.GenericType(rawType,types));
    }

    public abstract Object getValue(Ioc ioc);

    public String getName() {
        return name;
    }

    public Type getType() {
        return type;
    }

    @Override
    public int hashCode() {
        int h = this.h;
        if (h == 0) {
            if (type != null) {
                h = 31 * h + type.hashCode();
            }

            //h = 31 * h + classLoader.hashCode();
            if (name != null) {
                h = 31 * h + name.hashCode();
            }
            this.h = h;
        }
        return h;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof IocKey)) {
            return false;
        }

        IocKey key = (IocKey) obj;
        if (!Objects.equals(key.type, ((IocKey) obj).type)) {
            return false;
        }

        return StringUtils.equals(name, key.name);
    }

    @Override
    public String toString() {
        if (type == null) return name;
        if (name == null) return type.getTypeName();

        return type.getTypeName() + " : " + name;
    }

    private static IocKey fromParameter(Type type, Inject paramInject, Config config) {
        if (paramInject != null) {
            if (!StringUtils.isEmpty(paramInject.name())) {
                return new IocTypeKey(IocUtils.createGenericType(type), paramInject.name());
            } else if (!StringUtils.isEmpty(paramInject.config())) {
                return new IocConfigKey(type, paramInject.config());
            }
        } else if (config != null) {
            return new IocConfigKey(type, config.key(), config.file());
        } else {
            if (type instanceof Class && (Classes.isSimple((Class<?>) type) || type == Object.class)) {
                throw new IocException("找不到" + type + "的ioc配置");
            }

        }
        return new IocTypeKey(IocUtils.createGenericType(type));
    }

    private static boolean checkParameter(Type type, Inject paramInject, Config config) {
        if (paramInject != null) {
        } else if (config != null) {
        } else {
            return !(type instanceof Class) || (!Classes.isSimple((Class<?>) type) && type != Object.class);
        }
        return true;
    }

    /**
     * 替换泛型为实际
     *
     * @param parameterizedType
     * @param keyTypes
     * @param classTypes
     * @return
     */
    private static ParameterizedType replaceGenericType(ParameterizedType parameterizedType, Type[] keyTypes, TypeVariable[] classTypes) {
        assert (parameterizedType.getRawType() instanceof Class);
        Class rawType = (Class) parameterizedType.getRawType();
        Type[] types = parameterizedType.getActualTypeArguments();

        Type[] realTypes = new Type[types.length];
        int i = 0;
        for (Type tt : types) {
            realTypes[i] = getRealType(tt, keyTypes, classTypes);
            ++i;
        }
        return IocUtils.createGenericType(rawType, realTypes);
    }


}
